package function;

import kit.FileIO;
import domain.Movie;
import domain.Rating;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;

import static function.GobleVar.*;

public class Core
{
    private static ObjectMapper objectMapper = new ObjectMapper();

    public static void initMovies(ArrayList<String> arrayList,boolean createCache) throws JsonProcessingException
    {
        if (isDebug)
            GobleTool.printInfo("initMovies");
        for (int i=0;i<arrayList.size();i++)
        {
            String thisMovie = arrayList.get(i);
            String[] thisInfo = thisMovie.split("::");
            String[] tags = thisInfo[2].split("\\|");
            Movie movie = new Movie(Integer.parseInt(thisInfo[0]),thisInfo[1],tags);
            movieList.put(movie.getMovieId(),movie);
            if (isDebug)
                if (i%1000==0)
                    System.out.print("="+i/100+"%"+"=");
        }
        System.out.println();
        //DONE:释放临时内容，降低内存使用，加速计算
        GobleVar.moviesFile = null;
        if (createCache)
        {
            if (isDebug)
                GobleTool.printInfo("正在重新创建movieList缓存");
            String path="d:/teamdata/cache/system/movieList.dat";
            FileIO.deleteFile(path);
            for (Integer thisKey:movieList.keySet())
                FileIO.writeFile(path,thisKey + "::" + objectMapper.writeValueAsString(movieList.get(thisKey)));
        }
    }

    public static void initRatings(ArrayList<String> arrayList)
    {
        if (isDebug)
            GobleTool.printInfo("initRatings");
        for (int i=0;i<arrayList.size();i++)
        {
            String thisRating = arrayList.get(i);

            String thisInfo[] = thisRating.split("::");
            Rating rating = new Rating(Integer.parseInt(thisInfo[0]),
                                        Integer.parseInt(thisInfo[1]),
                                        Float.parseFloat(thisInfo[2]),
                                        thisInfo[3]);
            ratingList.add(rating);
            if (isDebug)
                if (i%900000==0)
                    System.out.print("="+i/90000+"%"+"=");
        }
        System.out.println();
        //DONE:释放临时内容，降低内存使用，加速计算
        GobleVar.ratingsFile = null;
    }

    public static void initMapping(boolean createCache) throws IOException
    {
        if (isDebug)
            GobleTool.printInfo("initMapping");
        int count = 0;
        for (int i=0;i<ratingList.size();i++)
        {
            Rating thisRating = ratingList.get(i);
            int thisMovieId = thisRating.getMovieId();
            int thisUserId = thisRating.getUserId();
            //TODO:MAPPING映射代码块重复，需与【MARK108】去重
            if (item_to_user.get(thisMovieId)==null)
            {
                HashMap<Integer,Float> tempMap = new HashMap<Integer, Float>();
                tempMap.put(thisUserId,thisRating.getRating());
                item_to_user.put(thisMovieId,tempMap);
            }
            else
            {
                item_to_user.get(thisMovieId).put(thisUserId,thisRating.getRating());
            }
            //TODO:等待去重代码块【MARK108】
            if (user_to_item.get(thisUserId)==null)
            {
                HashMap<Integer,Float> tempMap = new HashMap<Integer, Float>();
                tempMap.put(thisMovieId,thisRating.getRating());
                user_to_item.put(thisUserId,tempMap);
            }
            else
            {
                user_to_item.get(thisUserId).put(thisMovieId,thisRating.getRating());
            }
            count++;
            if (isDebug)
                if (count%900000==0)
                    System.out.print("="+count/90000+"%"+"=");
        }
        System.out.println();
        //DONE:释放临时内容，降低内存使用，加速计算
        GobleVar.ratingList = null;
        if (createCache)
        {
            if (isDebug)
                GobleTool.printInfo("正在重新创建Mapping缓存");
            String path1="d:/teamdata/cache/system/user_to_item";
            String path2="d:/teamdata/cache/system/item_to_user";
            if (isDebug)
                GobleTool.printInfo("正在重新创建Mapping缓存:分割Mapping");
            List<HashMap<Integer, HashMap<Integer, Float>>> list1 = GobleTool.mapChunk(new HashMap<>(user_to_item),5000);
            List<HashMap<Integer, HashMap<Integer, Float>>> list2 = GobleTool.mapChunk(new HashMap<>(item_to_user),500);
            if (isDebug)
                GobleTool.printInfo("正在重新创建Mapping:user_to_item缓存");
            int user_to_itemCount = list1.size();
            int item_to_userCount = list2.size();
            final CountDownLatch countDownLatch = new CountDownLatch(2) ;
            //TODO:线程代码去重，可以封装成方法进行调用，与【MARK112】重复
            new Thread(() ->
            {
                for (int i=0;i<user_to_itemCount;i++)
                {
                    GobleTool.printInfo("正在重新创建Mapping缓存:"+path1+i+".dat");
                    FileIO.deleteFile(path1+i+".dat");
                    try
                    {
                        FileIO.writeFile(path1+i+".dat",objectMapper.writeValueAsString(list1.get(i)));
                    } catch (JsonProcessingException e)
                    {
                        e.printStackTrace();
                    }
                /*//DONE:效率提升，直接序列化整个List集合
                    for (Integer thisKey:list1.get(i).keySet())
                        io.writeFile(path1+i+".dat",thisKey + "::" + objectMapper.writeValueAsString(list1.get(i).get(thisKey)));
                */
                }
                list1.clear();
                countDownLatch.countDown();
            }).start();

            if (isDebug)
                GobleTool.printInfo("正在重新创建Mapping:item_to_user缓存");
            //TODO:线程代码去重，可以封装成方法进行调用【MARK112】
            new Thread(() ->
            {
                for (int i=0;i<item_to_userCount;i++)
                {
                    GobleTool.printInfo("正在重新创建Mapping缓存:"+path2+i+".dat");
                    FileIO.deleteFile(path2+i+".dat");
                    try
                    {
                        FileIO.writeFile(path2+i+".dat",objectMapper.writeValueAsString(list2.get(i)));
                    } catch (JsonProcessingException e)
                    {
                        e.printStackTrace();
                    }
                    /*//DONE:效率提升，直接序列化整个List集合
                        for (Integer thisKey:list2.get(i).keySet())
                            io.writeFile(path2+i+".dat",thisKey + "::" + objectMapper.writeValueAsString(list2.get(i).get(thisKey)));
                    */
                }
                list2.clear();
                countDownLatch.countDown();
            }).start();
            try
            {
                countDownLatch.await();
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    public static ArrayList<String> readSimilarityCache(int movieId) throws IOException
    {
        if (isDebug)
            GobleTool.printInfo("正在查询缓存文件是否存在");
        ArrayList<String> cacheContent = FileIO.readFile("d:/teamdata/cache/"+ ((movieId/500)+1) +"/movies_"+movieId+".dat");
        if (cacheContent!=null)
            return cacheContent;
        else
        {
            GobleTool.printInfo("缓存不存在，正在处理推送计算任务");
            return Core.similarity(movieId);
        }
    }

    public static ArrayList<String> similarity(int movieId) throws IOException
    {
        long testTimeStart =  0;
        long testTimeStart2 =  0;

        if (item_to_user.size()==0 || user_to_item.size()==0 || movieList.size()==0)
        {
            GobleTool.printWaitInfo("未读入数据，请执行读取操作或加载缓存");
            return null;
        }
        long startTime =  System.currentTimeMillis();

        HashMap<Integer,Double> resultWithoutSort = new HashMap<>();
        HashMap<Integer,Float> ratingUserIds = item_to_user.get(movieId);
        HashSet<Integer> readyComMovie = new HashSet<>();//待比较的电影ID
        //MARK:先找出和哪些电影比较
            //MARK:找出评论此电影的用户
            //MARK:找出这些用户所评论过的电影

        if (ratingUserIds!=null && ratingUserIds.size()!=0)
        {
            for (int userId:ratingUserIds.keySet())
            {
                HashMap<Integer,Float> userRatingMovies = user_to_item.get(userId);
                readyComMovie.addAll(userRatingMovies.keySet());
            }

            readyComMovie.remove(movieId);
            //MARK:再计算本电影与这些电影的相似度
            for (int compMovieId:readyComMovie)
            {
                //DONE:原复制HashMap操作影响运行效率，已经改进，通过直接使用局部变量免除复制操作
/*              System.out.println(1);
                HashMap<Integer,Rating> userI = item_to_user.get(movieId);
                HashMap<Integer,Rating> userIcopy = new HashMap<Integer, Rating>(userI);
                System.out.println(2);*/

                double tempResult;
                HashMap<Integer,Float> userJ = item_to_user.get(compMovieId);

                if(userJ.size()>=100 && ratingUserIds.size()>=100)
                {
                    //FIXME:测试结束需要删除，计时器1
                    long a = System.currentTimeMillis();
                    Set<Integer> userList= new HashSet<>();
                    //DONE:优化,小量集合去重大量集合速度快
                    if (userJ.size()<ratingUserIds.size())
                    {
                        for (Integer t:userJ.keySet())
                            if (ratingUserIds.keySet().contains(t))
                                userList.add(t);
                        //DONE:优化,两个集合取交集速度优化
                        //userList = new HashSet<>(userJ.keySet());
                        //userList.retainAll(ratingUserIds.keySet());
                    }
                    else
                    {
                        for (Integer t:ratingUserIds.keySet())
                            if (userJ.keySet().contains(t))
                                userList.add(t);
                        //DONE:优化,两个集合取交集速度优化
                        //userList = new HashSet<>(ratingUserIds.keySet());
                        //userList.retainAll(userJ.keySet());
                    }
                    //FIXME:测试结束需要删除，计时器1
                    long b = System.currentTimeMillis();
                    testTimeStart += b-a;

                    float fenzi = 0;
                    float fenmu_front = 0;
                    float fenmu_rear = 0;
                    //FIXME:测试结束需要删除，计时器2
                    long aa = System.currentTimeMillis();
                    if(userList.size()>=100)
                    {
                        for (Integer userId : userList)
                        {
                            HashMap<Integer,Float> u = user_to_item.get(userId);
                            float tempN = u.get(movieId);
                            float tempM = u.get(compMovieId);

                            fenzi += tempN * tempM;
                            fenmu_front += tempN*tempN;
                            fenmu_rear += tempM*tempM;
                        }

                        fenmu_front = (float) Math.sqrt(fenmu_front);
                        fenmu_rear = (float) Math.sqrt(fenmu_rear);
                        tempResult = fenzi / (fenmu_front * fenmu_rear);
                    }
                    else
                        tempResult=0;
                    //FIXME:测试结束需要删除，计时器2
                    long bb = System.currentTimeMillis();
                    testTimeStart2 += bb-aa;
                }
                else
                    tempResult=0;
                resultWithoutSort.put(compMovieId,tempResult);
            }
        }
        resultWithoutSort.remove(movieId);

        List<Entry<Integer,Double>> list = new ArrayList<>(resultWithoutSort.entrySet());
        list.sort(Comparator.comparing(Entry::getValue));
        List<Entry<Integer,Double>> resultList;

        if (list.size()>=15)
            resultList = list.subList(list.size()-15,list.size());
        else
            resultList = list.subList(0,list.size());
        Collections.reverse(resultList);

        //DONE:封装结果集合
        ArrayList<String> resultArray = new ArrayList<>();
        for (int i=0;i<resultList.size();i++)
            resultArray.add((i+1)+":"+ movieList.get(resultList.get(i).getKey())+":"+resultList.get(i).getValue());
        //DONE:写入到缓存文件
        FileIO.writeFile("d:/teamdata/cache/"+ ((movieId/500)+1) +"/movies_"+movieId+".dat",resultArray);
        String path="d:/teamdata/cache/endingResult.dat";
        FileIO.writeFile(path,movieId+"::"+objectMapper.writeValueAsString(resultList));

        long endTime =  System.currentTimeMillis();
        double usedTime = (endTime-startTime)/1000.0;
        GobleTool.printInfo("本次处理用时:"+usedTime+"秒"+testTimeStart/1000.0+":::"+testTimeStart2/1000.0);
        return resultArray;
    }
}